#include <QtGui>
#include <QtCore>
#include <QtDebug>
#include <map>
#include <string>
#include <gloox/rostermanager.h>

#include "rosterwidget.h"
#include "../accountstore.h"
#include "../constants.h"
#include "../askpassworddialog.h"
#include "../chatwidgetstore.h"
#include "../settings.h"
#include "../xmpp/connectionstore.h"
#include "../xmpp/connection.h"
#include "../xmpp/glooxhelpers.h"
#include "../ui/ui_rosterwidget.h"
#include "../account.h"

struct RosterWidget::Private
{
    Ui::RosterWidget ui;
};

RosterWidget::RosterWidget(QWidget * parent, Qt::WindowFlags f)
    : QWidget(parent, f)
{
    d = new Private;
    d->ui.setupUi(this);

    // add global status combobox elements
    QStringList statusElements;
    statusElements << "available" << "away" << "na" << "busy" << "invisible";
    d->ui.globalStatusCombo->addItems(statusElements);
    d->ui.globalStatusCombo->insertSeparator(100);
    d->ui.globalStatusCombo->addItem("change status message");
    d->ui.globalStatusCombo->insertSeparator(100);
    d->ui.globalStatusCombo->addItem("offline");

    createActions();
    createMenus();
    initAccounts();
    retranslateUi();
}

RosterWidget::~RosterWidget()
{
    delete d;
}

void RosterWidget::retranslateUi()
{
    d->ui.retranslateUi(this);
    // retranslate other gui elements
    d->ui.globalStatusCombo->setItemText(0, tr("Available"));
    d->ui.globalStatusCombo->setItemText(1, tr("Away"));
    d->ui.globalStatusCombo->setItemText(2, tr("Not available"));
    d->ui.globalStatusCombo->setItemText(3, tr("Busy"));
    d->ui.globalStatusCombo->setItemText(4, tr("Invisible"));
    d->ui.globalStatusCombo->setItemText(6, tr("Change status message"));
    d->ui.globalStatusCombo->setItemText(8, tr("Offline"));
}

void RosterWidget::addAccount(int h)
{
    Kajim::AccountStore * accountStore = Kajim::AccountStore::instance();
    Kajim::XMPP::ConnectionStore * connectionStore =
        Kajim::XMPP::ConnectionStore::instance();

    Kajim::Account * account = accountStore->account(h);
    if (account == 0) {
        return;
    }

    QTreeWidgetItem * account_item = new QTreeWidgetItem(RosterWidget::ItemAccout);
    account_item->setData(0, ItemRoleAccountHandler, h);
    account_item->setText(0, account->param("name"));
    d->ui.contactsTree->addTopLevelItem(account_item);

    Kajim::XMPP::Connection * conn = connectionStore->accountConnection(h);
    connect(conn, SIGNAL(connected(int)),
            this, SLOT(accountOnline(int)));
    connect(conn, SIGNAL(disconnected(int)),
            this, SLOT(accountOffline(int)));
}

void RosterWidget::updateAccount(int h)
{
    Kajim::AccountStore * store = Kajim::AccountStore::instance();

    Kajim::Account * account = store->account(h);
    if (account == 0) {
        return;
    }

    QTreeWidgetItem * item = findAccountTreeItem(h);
    if (item != 0) {
        // TODO: change something else, maybe force relogin
         item->setText(0, account->param("name"));
    }
}

void RosterWidget::removeAccount(int h)
{
    Kajim::AccountStore * store = Kajim::AccountStore::instance();

    Kajim::Account * account = store->account(h);
    if (account == 0) {
        return;
    }

    // TODO: disconnect account before deleting
    QTreeWidgetItem * item = findAccountTreeItem(h);
    int index = d->ui.contactsTree->indexOfTopLevelItem(item);
    if (index != -1) {
        d->ui.contactsTree->takeTopLevelItem(index);
        delete item;
    }
}

void RosterWidget::logoutAll()
{
    Kajim::XMPP::ConnectionStore * connStore = Kajim::XMPP::ConnectionStore::instance();
    Kajim::AccountStore * accountStore = Kajim::AccountStore::instance();

    Q_FOREACH(int h, accountStore->accountsList()) {
        Kajim::XMPP::Connection * conn = connStore->accountConnection(h);
        conn->logout();
    }

}

void RosterWidget::createActions()
{
    statusActionGroup = new QActionGroup(this);
    statusOnlineAction = new QAction(tr("&Online"), this);
    statusActionGroup->addAction(statusOnlineAction);
    statusOfflineAction = new QAction(tr("O&ffline"), this);
    statusActionGroup->addAction(statusOfflineAction);

    Kajim::AccountStore * accountStore = Kajim::AccountStore::instance();
    connect(statusActionGroup, SIGNAL(triggered(QAction *)), this, SLOT(changeStatus(QAction *)));
    connect(accountStore, SIGNAL(AccountAdded(int)),
            this, SLOT(addAccount(int)));
    connect(accountStore, SIGNAL(AccountRemoved(int)),
            this, SLOT(removeAccount(int)));
    connect(accountStore, SIGNAL(AccountUpdated(int)),
            this, SLOT(updateAccount(int)));
    connect(d->ui.contactsTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(itemDoubleClick(QTreeWidgetItem *,int)));
}

//! \brief Create context menus
void RosterWidget::createMenus()
{
    accountMenu = new QMenu(this);
    QMenu * statusMenu = new QMenu(tr("Status"));
    statusMenu->addAction(statusOnlineAction);
    statusMenu->addSeparator();
    statusMenu->addAction(statusOfflineAction);
    accountMenu->addMenu(statusMenu);
}

void RosterWidget::initAccounts()
{
    Kajim::AccountStore * store = Kajim::AccountStore::instance();

    Q_FOREACH(int h, store->accountsList()) {
        addAccount(h);
    }
}
//! \brief Change account network status
void RosterWidget::changeStatus(QAction * action)
{
    // find selected account
    QTreeWidgetItem * item = d->ui.contactsTree->currentItem();
    if (item == 0 || item->type() != RosterWidget::ItemAccout) {
        return;
    }

    Kajim::XMPP::ConnectionStore * connStore = Kajim::XMPP::ConnectionStore::instance();
    Kajim::AccountStore * accountStore = Kajim::AccountStore::instance();

    int h = item->data(0, ItemRoleAccountHandler).toInt();
    Kajim::Account * account = accountStore->account(h);
    Kajim::XMPP::Connection * conn = connStore->accountConnection(h);

    // find chosen action
    if (action == statusOfflineAction && conn->presence() != gloox::PresenceUnavailable) {
        qDebug() << "go offline, account: " << item->text(0);
        conn->logout();
        return;
    }

    if (account->param("stored_password") == "") {
        if (account->param("save_password") != "true") {
            // password is not saved so ask user
            QString label = tr("Enter password for the account “%1”").arg(account->param("name"));
            AskPasswordDialog dialog(label, "Enter password", this);
            if (dialog.exec() == QDialog::Rejected) {
                return;
            }
            QString password = dialog.password();
            account->setParam("stored_password", password);
            if (dialog.rememberPassword()) {
                account->setParam("save_password", "true");
                account->setParam("password", password);
            } else {
                account->setParam("save_password", "");
            }
        } else {
            // take password from the config
            QString password = account->param("password");
            account->setParam("stored_password", password);
        }
        accountStore->sync();
    }
    gloox::Presence p = gloox::PresenceUnknown;

    if (action == statusOnlineAction) {
        p = gloox::PresenceAvailable;
    }

    if (p != gloox::PresenceUnknown && p != gloox::PresenceUnavailable) {
        qDebug() << "go online, account: " << item->text(0);
        conn->login(gloox::PresenceAvailable);
    }
}

void RosterWidget::accountOnline(int h)
{
    qDebug() << "Account online";
    Kajim::XMPP::ConnectionStore * connStore = Kajim::XMPP::ConnectionStore::instance();
    Kajim::XMPP::Connection * conn = connStore->accountConnection(h);

    gloox::RosterManager * rm = conn->rosterManager();
    if (rm == 0) {
        accountOffline(h);
        return;
    }

    typedef QList<gloox::RosterItem*> RosterItemList;
    QHash<QString, RosterItemList> groups;
    RosterItemList no_group;

    gloox::Roster * roster = rm->roster();
    for (gloox::Roster::iterator i=roster->begin(); i != roster->end(); i++) {
        gloox::RosterItem * ri = i->second;
        const gloox::StringList & rig = ri->groups();

        for (gloox::StringList::const_iterator j=rig.begin(); j!=rig.end(); j++) {
            QString g = QString::fromStdString(*j);
            groups[g] << ri;
        }

        if (rig.size() == 0) {
            no_group << ri;
        }
    }

    // create groups items
    QTreeWidgetItem * account_item = findAccountTreeItem(h);
    if (account_item == 0) {
        return;
    }

    Kajim::AccountStore * accountStore = Kajim::AccountStore::instance();
    Kajim::Account * account = accountStore->account(h);

    QString key = Kajim::Settings::ACCOUNT_PARAM_KEY_TEMPLATE.arg(account->param("name")) + "expanded_groups";
    QStringList expanded_groups = Kajim::Settings::get(key).toStringList();

    Q_FOREACH (const QString & group_name, groups.uniqueKeys()) {
        QTreeWidgetItem * group_item = new QTreeWidgetItem(account_item, ItemGroup);
        group_item->setText(0, group_name);
        Q_FOREACH(gloox::RosterItem* ri, groups[group_name]) {
            QTreeWidgetItem * contact_item = new QTreeWidgetItem(group_item, ItemContact);
            contact_item->setText(0, Kajim::XMPP::getNameFromRosterItem(ri));
            contact_item->setData(0, ItemRoleContactJid, Kajim::XMPP::getJidFromRosterItem(ri));
        }
        // expand group if required
        if (expanded_groups.contains(group_name)) {
            group_item->setExpanded(true);
        }
    }

    if (no_group.count() != 0) {
        QTreeWidgetItem * group_item = new QTreeWidgetItem(account_item, ItemDefaultGroup);
        group_item->setText(0, tr("<no group>"));
        Q_FOREACH(gloox::RosterItem* ri, no_group) {
            QTreeWidgetItem * contact_item = new QTreeWidgetItem(group_item, ItemContact);
            contact_item->setText(0, Kajim::XMPP::getNameFromRosterItem(ri));
        }
        key = Kajim::Settings::ACCOUNT_PARAM_KEY_TEMPLATE.arg(account->param("name")) + "expanded_default_group";
        if (Kajim::Settings::get(key).toString() == "true") {
            group_item->setExpanded(true);
        }
    }

    key = Kajim::Settings::ACCOUNT_PARAM_KEY_TEMPLATE.arg(account->param("name")) + "roster_expanded";
    if (Kajim::Settings::get(key) == "true") {
        // expand account item
        account_item->setExpanded(true);
    }
}

void RosterWidget::accountOffline(int h)
{
    qDebug() << "RosterWidget::accountOffline() call";

    QTreeWidgetItem * account_item = findAccountTreeItem(h);
    if (account_item == 0) {
        return;
    }

    Kajim::AccountStore * accountStore = Kajim::AccountStore::instance();
    Kajim::Account * account = accountStore->account(h);

    // remember account item state: expanded or not
    QString key = Kajim::Settings::ACCOUNT_PARAM_KEY_TEMPLATE.arg(account->param("name")) + "roster_expanded";
    QString expanded(account_item->isExpanded() ? "true" : "");
    Kajim::Settings::set(key, expanded);

    // remove contents of roster for given account

    QStringList expanded_groups;
    int child_count = account_item->childCount();

    key = Kajim::Settings::ACCOUNT_PARAM_KEY_TEMPLATE.arg(account->param("name")) + "expanded_default_group";
    for (int i=0; i<child_count; i++) {
        // we always take item 0 because we delete item on each step
        QTreeWidgetItem * group_item = account_item->child(0);
        if (group_item == 0) {
            continue;
        }

        // remember groups expanding state
        QString group_name = group_item->text(0);


        if (group_item->type() == RosterWidget::ItemDefaultGroup) {
            if (group_item->isExpanded()) {
                Kajim::Settings::set(key, "true");
            } else {
                Kajim::Settings::set(key, "");
            }
        } else {
            if (group_item->isExpanded()) {
                expanded_groups << group_name;
            }
        }

        group_item = account_item->takeChild(0);
        delete group_item;
    }

    key = Kajim::Settings::ACCOUNT_PARAM_KEY_TEMPLATE.arg(account->param("name")) + "expanded_groups";
    Kajim::Settings::set(key, expanded_groups);
}

void RosterWidget::itemDoubleClick(QTreeWidgetItem * item, int column)
{
    if (item->type() == RosterWidget::ItemContact) {
        // find contact parent account
        QTreeWidgetItem * parent_account = item->parent()->parent();
        int parent_account_handler = parent_account->data(0, RosterWidget::ItemRoleAccountHandler).toInt();

        qDebug() << "open dialog window";
        QString peerJid = item->data(0, ItemRoleContactJid).toString();

        // Initialize message session
        // Each message session could be linked with two kind of peers:
        //   * peer with "short" JID (e.g. user@example.net)
        //   * peer with full JID (e.g. user@example.net/resource)
        // When user double clicks contact item first kind of session is created.

        // When new message arrives from the peer it's routed to single chat window,
        // if there is chat window with exactly same peerJid then it's routed there;
        // elsif there is chat window with "short" JID then message is routed there;
        // finally if there is no opened chat windows for that peerJid new window is
        // created with "short" peerJid and message is routed there.
        Kajim::XMPP::ConnectionStore * connectionStore = Kajim::XMPP::ConnectionStore::instance();
        Kajim::XMPP::Connection * conn = connectionStore->accountConnection(parent_account_handler);
        //conn->initMessageSession(peerJid);

        Kajim::ChatWidgetStore * chatsStore = Kajim::ChatWidgetStore::instance();
        chatsStore->chatWidget(parent_account_handler, peerJid);
    }
}

QTreeWidgetItem * RosterWidget::findAccountTreeItem(int h)
{
    int count = d->ui.contactsTree->topLevelItemCount();
    QTreeWidgetItem * res = 0;

    for (int i=0; i<count; i++) {
        QTreeWidgetItem * item = d->ui.contactsTree->topLevelItem(i);
        if (item->type() != RosterWidget::ItemAccout) {
            continue;
        }
        if (item->data(0, ItemRoleAccountHandler).toInt() == h) {
            res = item;
        }
    }

    return res;
}

void RosterWidget::contextMenuEvent(QContextMenuEvent * e)
{
    // detect what item is under the mouse pointer
    QTreeWidgetItem * item = d->ui.contactsTree->itemAt(e->pos());
    if (item != 0) {
        QMenu * menu = 0;

        switch (static_cast<RosterWidget::TreeItemType>(item->type())) {
        case RosterWidget::ItemAccout:
            menu = accountMenu;
            break;
        case RosterWidget::ItemContact:
            break;
        case RosterWidget::ItemGroup:
        case RosterWidget::ItemDefaultGroup:
            break;
        }

        if (menu != 0) {
            menu->exec(e->globalPos());
        }
    }
}

